// PART OF THE MACHINE SIMULATION. DO NOT CHANGE.

package nachos.machine;

import nachos.security.*;

import java.util.TreeSet;
import java.util.Iterator;
import java.util.SortedSet;

/**
 * The <tt>Interrupt</tt> class emulates low-level interrupt hardware. The
 * hardware provides a method (<tt>setStatus()</tt>) to enable or disable
 * interrupts.
 *
 * <p>
 * In order to emulate the hardware, we need to keep track of all pending
 * interrupts the hardware devices would cause, and when they are supposed to
 * occur.
 *
 * <p>
 * This module also keeps track of simulated time. Time advances only when the
 * following occur:
 * <ul>
 * <li>interrupts are enabled, when they were previously disabled
 * <li>a MIPS instruction is executed
 * </ul>
 *
 * <p>
 * As a result, unlike real hardware, interrupts (including time-slice context
 * switches) cannot occur just anywhere in the code where interrupts are
 * enabled, but rather only at those places in the code where simulated time
 * advances (so that it becomes time for the hardware simulation to invoke an
 * interrupt handler).
 *
 * <p>
 * This means that incorrectly synchronized code may work fine on this hardware
 * simulation (even with randomized time slices), but it wouldn't work on real
 * hardware. But even though Nachos can't always detect when your program
 * would fail in real life, you should still write properly synchronized code.
 */
public final class Interrupt {
    /**
     * Allocate a new interrupt controller.
     *
     * @param privilege encapsulates privileged access to the Nachos
     *                  machine.
     */
    public Interrupt(Privilege privilege) {
        System.out.print(" interrupt");

        this.privilege = privilege;
        privilege.interrupt = new InterruptPrivilege();

        enabled = false;
        pending = new TreeSet();
    }

    /**
     * 开中断
     */
    public void enable() {
        setStatus(true);
    }

    /**
     * 关中断并且返回之前的中断状态
     *
     * @return 之前中断是否打开
     */
    public boolean disable() {
        return setStatus(false);
    }

    /**
     * 将中断恢复到指定状态
     *
     * @param status true 表示要打开中断，false 表示要关闭中断
     */
    public void restore(boolean status) {
        setStatus(status);
    }

    /**
     * 打开或关闭中断，并返回之前的状态
     * 如果中断状态从禁用变为启用，则模拟时钟提前
     *
     * @param status true 表示开中断，false 表示关中断
     * @return 中断之前的状态
     */
    public boolean setStatus(boolean status) {
        boolean oldStatus = enabled;
        enabled = status;

        // 如果以前是关中断而现在是开中断，则 tick()
        if (oldStatus == false && status == true)
            tick(true);

        return oldStatus;
    }

    /**
     * 测试中断是否被打开
     *
     * @return 中断是否是打开状态
     */
    public boolean enabled() {
        return enabled;
    }

    /**
     * 测试中断是否被关闭
     *
     * @return 如果中断是否是关闭状态
     */
    public boolean disabled() {
        return !enabled;
    }

    private void schedule(long when, String type, Runnable handler) {
        Lib.assertTrue(when > 0);

        long time = privilege.stats.totalTicks + when;
        PendingInterrupt toOccur = new PendingInterrupt(time, type, handler);

        Lib.debug(dbgInt, "Scheduling the " + type + " interrupt handler at time = " + time);

        pending.add(toOccur);
    }

    // tick 时钟前进
    private void tick(boolean inKernelMode) {
        Stats stats = privilege.stats;

        // 不同情况下前进的时钟数目不同
        // inKernelMode == true 指的是在关中断后开中断的时候 tick，此时前进 10 个系统时钟
        // inKernelMode == false 指的是在执行了一条用户程序的指令之后 tick，此时前进 1 个系统时钟
        if (inKernelMode) {
            stats.kernelTicks += Stats.KernelTick;
            stats.totalTicks += Stats.KernelTick;
        } else {
            stats.userTicks += Stats.UserTick;
            stats.totalTicks += Stats.UserTick;
        }

        if (Lib.test(dbgInt))
            System.out.println("== Tick " + stats.totalTicks + " ==");

        enabled = false;
        checkIfDue(); // 调用 checkDue 检查中断
        enabled = true;
    }

    private void checkIfDue() {

        // 检测一些东西
        long time = privilege.stats.totalTicks;

        Lib.assertTrue(disabled());

        if (Lib.test(dbgInt))
            print();

        if (pending.isEmpty())
            return;

        if (((PendingInterrupt) pending.first()).time > time)
            return;

        Lib.debug(dbgInt, "Invoking interrupt handlers at time = " + time);

        // 从 pending 容器中取出中断，并且将已经到了触发事件和中断触发
        while (!pending.isEmpty() &&
                ((PendingInterrupt) pending.first()).time <= time) {
            PendingInterrupt next = (PendingInterrupt) pending.first();
            pending.remove(next);

            Lib.assertTrue(next.time <= time);

            if (privilege.processor != null)
                privilege.processor.flushPipe();

            Lib.debug(dbgInt, "  " + next.type);

            // 调用中断的处理函数
            next.handler.run();
        }

        Lib.debug(dbgInt, "  (end of list)");
    }

    private void print() {
        System.out.println("Time: " + privilege.stats.totalTicks
                + ", interrupts " + (enabled ? "on" : "off"));
        System.out.println("Pending interrupts:");

        for (Iterator i = pending.iterator(); i.hasNext(); ) {
            PendingInterrupt toOccur = (PendingInterrupt) i.next();
            System.out.println("  " + toOccur.type +
                    ", scheduled at " + toOccur.time);
        }

        System.out.println("  (end of list)");
    }

    private class PendingInterrupt implements Comparable {
        PendingInterrupt(long time, String type, Runnable handler) {
            this.time = time;
            this.type = type;
            this.handler = handler;
            this.id = numPendingInterruptsCreated++;
        }

        public int compareTo(Object o) {
            PendingInterrupt toOccur = (PendingInterrupt) o;

            // can't return 0 for unequal objects, so check all fields
            if (time < toOccur.time)
                return -1;
            else if (time > toOccur.time)
                return 1;
            else if (id < toOccur.id)
                return -1;
            else if (id > toOccur.id)
                return 1;
            else
                return 0;
        }

        long time;
        String type;
        Runnable handler;

        private long id;
    }

    private long numPendingInterruptsCreated = 0;

    private Privilege privilege;

    private boolean enabled;
    private TreeSet pending;

    private static final char dbgInt = 'i';

    private class InterruptPrivilege implements Privilege.InterruptPrivilege {
        public void schedule(long when, String type, Runnable handler) {
            Interrupt.this.schedule(when, type, handler);
        }

        public void tick(boolean inKernelMode) {
            Interrupt.this.tick(inKernelMode);
        }
    }
}
